package core

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"mime"
	"mytool/base"
	"mytool/utils"
	"net/http"
	"os"
	"path"
	"strconv"
	"strings"
	"time"
)

type ShareResponseData struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
}

func ErrorResponse(w http.ResponseWriter, err error) {
	bytes, _ := json.Marshal(&ShareResponseData{201, err.Error()})
	w.Header().Add("Content-Type", "application/json;charset=utf-8")
	w.Write(bytes)
}

func ResponseTips(w http.ResponseWriter, message string) {
	bytes, _ := json.Marshal(&ShareResponseData{200, message})
	w.Header().Add("Content-Type", "application/json;charset=utf-8")
	w.Write(bytes)
}

func uploadFile(w http.ResponseWriter, r *http.Request, uploadPath string, enableUpload bool) {
	if !enableUpload {
		return
	}

	err := r.ParseMultipartForm(32 << 20) // 解析表单数据,最大限制为32MB
	if err != nil {
		ErrorResponse(w, err)
		return
	}

	files := r.MultipartForm.File["files"]
	for _, file := range files {
		// 创建上传的文件
		outFilePath := utils.NotCoverPath(uploadPath, file.Filename)
		outFile, err := os.Create(outFilePath)
		if err != nil {
			ErrorResponse(w, err)
			return
		}
		defer outFile.Close()

		// 打开上传的文件
		inFile, err := file.Open()
		if err != nil {
			ErrorResponse(w, err)
			return
		}
		defer inFile.Close()

		// 写入文件
		_, err = io.Copy(outFile, inFile)
		if err != nil {
			ErrorResponse(w, err)
			return
		}
		utils.Log(outFilePath, "写入成功!")
	}
	ResponseTips(w, "上传完毕!")
}

func gnerateFileItemInfo(fileInfo os.FileInfo, requestPath string) string {
	// 文件类型
	tips := "下载"
	atrributeName := "download"

	// 文件大小
	size := utils.SmartSize(fileInfo.Size())
	if fileInfo.IsDir() {
		tips = "进入"
		atrributeName = ""
		size = ""
	}

	// 文件的名称
	name := fileInfo.Name()

	// 下载的路径
	downloadPath := requestPath + "/" + name
	if requestPath == "/" {
		downloadPath = requestPath + name
	}

	visitedTime := fileInfo.ModTime().Format(utils.TimeFmt)

	// 生成下载链接,参考链接 https://www.jb51.net/web/601923.html
	if fileInfo.IsDir() {
		contentFmt := `<tr class="fileItem"><td><a href="%s" style="color:#1890ff">%s</a></td><td>%s</td><td>%s</td><td><a href="%s" %s>%s</a></td></tr>`
		return fmt.Sprintf(contentFmt, downloadPath, name, visitedTime, size, downloadPath, atrributeName, tips)
	} else {
		contentFmt := `<tr class="fileItem"><td style="cursor: pointer" onclick="doActions('%s')">%s</td><td>%s</td><td>%s</td><td><a href="%s" %s>%s</a></td></tr>`
		return fmt.Sprintf(contentFmt, downloadPath, name, visitedTime, size, downloadPath, atrributeName, tips)
	}

}

// ResponseDownloadPage 生成下载页面
func responseDownloadPage(writer http.ResponseWriter, basePath, requestPath string, enableUpload bool) {
	html := ""
	fullPath := basePath + requestPath

	// 遍历文件
	fileInfos, _ := ioutil.ReadDir(fullPath)
	for _, fileInfo := range fileInfos {
		html += gnerateFileItemInfo(fileInfo, requestPath)
	}

	if len(fileInfos) == 0 {
		html = `<tr><td style="color:red">当前目录为空</td><td></td><td></td><td></td></tr>`
	}

	// 发送给客户端
	writer.Header().Add("Content-Type", "text/html;charset=utf8")
	writer.Write([]byte(HtmlHeader))
	writer.Write([]byte(html))
	writer.Write([]byte(HtmlFooter1))
	writer.Write([]byte(utils.Sign()))
	writer.Write([]byte(HtmlFooter2))
	writer.Write([]byte(JsScript))
	if !enableUpload {
		writer.Write([]byte(HideUpload))
	}
}

func registShare(basePath string, enableUpload bool) {
	// 使用"/"可以匹配所有的请求
	http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
		// 1.判断请求的对象
		utils.Log("From", request.RemoteAddr, "访问：", request.URL.Path)
		requestFile := basePath + request.URL.Path

		// 1.1 文件不存在
		if utils.PathExists(requestFile) == false {
			utils.Log(requestFile, "No found!")
			http.NotFound(writer, request)
			return
		}

		// 1.2 指定的文件是文件夹
		if utils.IsDir(requestFile) {
			if request.Method == http.MethodPut {
				uploadFile(writer, request, requestFile, enableUpload)
				return
			}
			responseDownloadPage(writer, basePath, request.URL.Path, enableUpload)
			return
		}

		// 2 响应数据
		file, err := os.Open(requestFile)
		if err != nil {
			http.Error(writer, err.Error(), 500)
			return
		}
		defer file.Close()

		// 2.1 设置响应的MIME类型
		ext := path.Ext(request.URL.Path)
		typeByExtension := mime.TypeByExtension(ext)
		writer.Header().Add("Content-Type", typeByExtension)

		// 2.2 读文件发送给客户端
		buffer := make([]byte, 1024)
		for {
			n, err := file.Read(buffer)
			if err == io.EOF {
				break
			}
			if err != nil {
				utils.Log(err.Error())
				break
			}
			writer.Write(buffer[:n])
		}
	})
}

func HttpFileShare(args []string) (err error) {
	// 1.处理参数
	argsLen := len(args)
	sharePath := args[1]
	sharePath = strings.ReplaceAll(sharePath, `\`, "/")
	port := "80"
	if argsLen < 2 || argsLen > 4 {
		return errors.New("参数错误！")
	}

	// 判断是否开启上传功能
	enableUpload := args[argsLen-1] == "-upload"

	// 判断用户是否自定义监听端口
	if argsLen == 4 || (argsLen == 3 && !enableUpload) {
		port = args[2]
	}

	// 校验端口
	_, err = strconv.Atoi(port)
	if err != nil {
		return errors.New("请输入正确的端口号")
	}
	address := ":" + port

	// 分享的路径判断
	if !utils.PathExists(sharePath) {
		return errors.New("分享的路径不存在！")
	}

	if !utils.IsDir(sharePath) {
		return errors.New("请将文件放置在一个目录中,然后共享该路径,不包括文件名!")
	}

	// 2.注册共享
	registShare(sharePath, enableUpload)

	// 3.提示用户
	go func() {
		time.Sleep(time.Second * 2)
		res, err := http.Get("http://localhost:" + port)
		if err != nil {
			utils.Log(err.Error())
			return
		}
		defer res.Body.Close()

		utils.Out("你已经共享:", sharePath, "你可以通过如下方式访问共享资源：")
		for _, ip := range GetIpaddress() {
			utils.Out("http://" + ip + ":" + port)
		}
	}()

	// 4.启动http服务
	return http.ListenAndServe(address, nil)
}

func ShareTools() []*base.Option {
	return []*base.Option{
		&base.Option{"share", HttpFileShare, `share <path [port] [-upload]> eg. share d:\files 8080  使用HTTP方式分享指定文件,注意path不带文件名,如果加上-upload,则会开启上传功能 `},
	}
}
